#include "uthread.h"
#include "types.h"
#include "user.h"
#include "fcntl.h"
#define LONG_EATING_PROCESS_ITERATIONS 100000
#define SHORT_EATING_PROCESS_ITERATIONS 1000

//Our menu consists of three kinds of food: Salad, Pasta and Steak, assigned values 0, 1 and 2 respectively.
#define SALAD 0
#define PASTA 1
#define STEAK 2

#define Students_Initial 0
#define Students_Joining 1
#define Num_of_seats 2
#define Salad_Buffer_Size 3
#define Pasta_Buffer_Size 4
#define Steak_Buffer_Size 5

#define TABLE 0
#define STUDENTS 1

#define p_student 3
#define p_waiter_host 1
#define p_normal 2

int iter_long = LONG_EATING_PROCESS_ITERATIONS;
int iter_short = SHORT_EATING_PROCESS_ITERATIONS;
int totalThreadsNum = 1;
int food[3];
int foodMAX[3];
int restaurant[2];
int restaurantMAX[2];
int exitCount =0;
int exitFlag = 0;
int waitingQueue = 0;
int idMAX = 0;
int* studThreadsIds_;
int logFileDescriptor;

void printDebug(char* name){
//   printf(1,"%s\t\t tid:%d id:%d stud:%d pr:%d table:%d\n",name,uthread_self().tid, studThreadsIds_[uthread_self().tid], restaurant[STUDENTS], uthread_getpr(),restaurant[TABLE]);
}

//When simulation ends, print the results of the simulation
//TODO: totalElapsedTime == uptime() - ptable.proc->ctime
void printStats(){
    int totalElapsedTime = 0, totalRunTime=0, totalWaitTime=0;
    //if (retrieve_process_statistics(&totalElapsedTime,&totalRunTime,&totalWaitTime)>0) {
    retrieve_process_statistics(&totalElapsedTime,&totalRunTime,&totalWaitTime);
        //In order to calculate average statistics per thread,
        //we will take total run_time and total_WAITING_time of the process and divide it by number of created threads.
        printf(logFileDescriptor,"Simulation: User-Level-Threads\n");
        printf(logFileDescriptor,"Total run time is: %d\n",totalElapsedTime);
        printf(logFileDescriptor,"Average run time per user-level-thread is: %d\n",totalRunTime/totalThreadsNum);
        printf(logFileDescriptor,"Average wait time per user-level thread is: %d\n",totalWaitTime/totalThreadsNum);
        close(logFileDescriptor);
    //}
}

//void getNextStud_id(){
//    currStud_id++;
//    if (currStud_id == idMAX) {
//        currStud_id = idMIN;
//    }
//}


/********************
 Table        
 ********************/
//A WAITING student may join the table only when a seat become available, i.e. another student has finished dinning and left the table.
//Arriving students should be assigned a thread only after being seated by the host.
//The operation of adding new students to the table can be imagined as a host that welcomes new students and leads them to their seat.
//The host must verify first that there are any available seats.
//Otherwise, he should keep the students WAITING.
//When a seat has become available,
//the host notifies a WAITING student and puts him in his seat.
void callHost(){
    studThreadsIds_[uthread_self().tid] = -1;
    while (studThreadsIds_[uthread_self().tid] == -1 && (restaurant[STUDENTS] > 0)) {
        uthread_yield();            
    }
    
    if ((restaurant[STUDENTS] > 0)) {
        printf(logFileDescriptor,"Student%d joined the table\n",studThreadsIds_[uthread_self().tid]);        
    }
}

void host(){
    int i;
    while (restaurant[STUDENTS]>0) {
        if (restaurant[TABLE] == 0) {
            uthread_setpr(p_waiter_host);
        }
        for (i=0; i<totalThreadsNum && (restaurant[TABLE] < restaurantMAX[TABLE]) ; i++) {
            if(studThreadsIds_[i] == -1){
                restaurant[TABLE]++;
                idMAX++;
                studThreadsIds_[i] = idMAX;
                printDebug("+inviting");
                break;
            }
        }
        
        if (restaurant[TABLE] == restaurantMAX[TABLE]) {
            uthread_setpr(p_normal);
        }
        uthread_yield();            
    }
    printDebug("--hostExit");
}


/********************
        Student
 ********************/

/*
 -Eating process
 The following pseudo code should be applied by a thread after it successfully acquired the first two kinds of food (long eating process) and after acquiring the last one (short eating process).
 -Note the parameter iter_num is different for each type of eating.
 
 For i:1 to iter_num For j:1 to 1000
 t= 1
 For k:1 to 20
 t *= k;
 */
void eat(int iter_num){
    //printDebug("EatingStart");
    printf(logFileDescriptor,"Student%d started %s eating process\n",studThreadsIds_[uthread_self().tid],iter_num == SHORT_EATING_PROCESS_ITERATIONS ? "short" : "long");
    int t,k,i,j;
    i=1;
    uthread_yield();
    for (i=1; i<iter_num; i++) {
        for (j=1; j<1000; j++) {
            t=1;
            for (k=1; k<20; k++) {
                t *= k;
            }
        }
    }
    //printDebug("EatingEnd");


}

//Each student must acquire all three kinds of food,

//TODO(kthread):The buffers must be synchronized, which means only one student can use a specific buffer at a given time.
void accuireFood(){
    if (studThreadsIds_[uthread_self().tid] == -1) {
        callHost();
    }
    while (restaurant[STUDENTS] > 0) {
        restaurant[STUDENTS]--;//one less student to worry about
        uthread_yield();
        //First, each student acquires food type with (stud_id % 3) value assigned to it.
        while (food[studThreadsIds_[uthread_self().tid] % 3] == 0){
            printf(logFileDescriptor,"Student%d waits for %d\n",studThreadsIds_[uthread_self().tid],studThreadsIds_[uthread_self().tid] % 3);
            uthread_yield();
        }
        //printDebug("accuireFood1");
        printf(logFileDescriptor,"Student%d acquired %d\n",studThreadsIds_[uthread_self().tid],studThreadsIds_[uthread_self().tid] % 3);
        food[studThreadsIds_[uthread_self().tid] % 3]--;

             
        //Second, the student acquires food type with ( (stud_id+1) % 3 ) value assigned to it.
        while (food[(studThreadsIds_[uthread_self().tid]+1) % 3] == 0){
            printf(logFileDescriptor,"Student%d waits for %d\n",studThreadsIds_[uthread_self().tid],studThreadsIds_[uthread_self().tid] % 3);
            uthread_yield();
        }
        //printDebug("accuireFood2");
        printf(logFileDescriptor,"Student%d acquired %d\n",studThreadsIds_[uthread_self().tid],(studThreadsIds_[uthread_self().tid]+1) % 3);
        food[(studThreadsIds_[uthread_self().tid]+1) % 3]--;
        
        //At this point, the student must execute a long eating process on the 2 dishes acquired.
        eat(iter_long);

        //When the long eating process is done, the third and last dish ( (stud_id + 2) % 3) is to be acquired
        while (food[(studThreadsIds_[uthread_self().tid]+2) % 3] == 0){
            printf(logFileDescriptor,"Student%d waits for %d\n",studThreadsIds_[uthread_self().tid],studThreadsIds_[uthread_self().tid] % 3);
            uthread_yield();
        }
        //printDebug("accuireFood3");
        printf(logFileDescriptor,"Student%d acquired %d\n",studThreadsIds_[uthread_self().tid],(studThreadsIds_[uthread_self().tid]+2) % 3);
        food[(studThreadsIds_[uthread_self().tid]+2) % 3]--;
                
        //which leads to a short eating process.
        eat(iter_short);
        restaurant[TABLE]--;
        //printDebug("**leaving");
        printf(logFileDescriptor,"Student%d leaved the table\n",studThreadsIds_[uthread_self().tid]);
        callHost();
    }
    //When the student has finished eating the last dish he leaves the table (the thread representing the student exits).
    //The simulation ends after all students have eaten all three dishes.

    exitCount++;
    if (exitCount == totalThreadsNum-4) {
        exitFlag = 1;
        printStats();
    }
    printDebug("--exitRest");
}



/********************
        Food        
********************/

//Only one item of food type can be added in a single operation of the waiter.
//Each food type has its own buffer that is maintained and refilled by a designated waiter.
//This (eager) waiter refills his buffer whenever it is not full.
//TODO(kthread):Also, a waiter can refill his buffer only in case it is not occupied by another student.

void fillSalad(){
    while (exitFlag == 0) {
        if (food[SALAD] == 0) {
            uthread_setpr(p_waiter_host);
        }
        if (food[SALAD] < foodMAX[SALAD]) {
            food[SALAD]++;
            //printDebug("+fillSalad");
            printf(logFileDescriptor,"WaiterSalad increased his buffer to %d/%d\n",food[SALAD],foodMAX[SALAD]);
        }
        if (food[SALAD] == foodMAX[SALAD]) {
            uthread_setpr(p_normal);
        }
        uthread_yield();            
    }
    printDebug("--SaladExit");
}

void fillSteak(){
    while (exitFlag == 0) {
        if (food[STEAK] == 0) {
            uthread_setpr(p_waiter_host);
        }
        if (food[STEAK] < foodMAX[STEAK]) {

            food[STEAK]++;
            //printDebug("+fillSteak");
            printf(logFileDescriptor,"WaiterSteak increased his buffer to %d/%d\n",food[STEAK],foodMAX[STEAK]);
        }
        if (food[STEAK] == foodMAX[STEAK]) {
            uthread_setpr(p_normal);
        }
        uthread_yield();        
    }
    printDebug("--SteakExit");

}

void fillPasta(){
    while (exitFlag == 0) {
        if (food[PASTA] == 0) {
            uthread_setpr(p_waiter_host);
        }
        if (food[PASTA] < foodMAX[PASTA]) {

            food[PASTA]++;
            //printDebug("+fillPasta");
            printf(logFileDescriptor,"WaiterPasta increased his buffer to %d/%d\n",food[PASTA],foodMAX[PASTA]);
        }
        if (food[PASTA] == foodMAX[PASTA]) {
            uthread_setpr(p_normal);
        }
        uthread_yield();         
    }
    printDebug("--PastaExit");

}


/*
 We model a restaurant. 
 it up to you decide when and where threads should yield.
 The threads must cooperate in order to complete the simulation.
 */
int main(int argc, char** argv){
    //Buffers sizes will be given in a configuration file as well, and will remain constant during the simulation.
    int i = 0, j = 0, m =0;
    int args[6] = { [0 ... 5] = 0 } ;
    int fp = open("ass2_conf.txt",O_RDONLY);
    char buf[1024];
    char value[5] = { '\0' };
    //printf(1,"here %d %d\n",fp,read( fp, line, sizeof(line)));
    if(read( fp, &buf, sizeof(buf) ) > 0){
        while (j<6) {
            while (buf[i] != '=' && i< sizeof(buf)) {
                //printf(1,"%c\n",buf[i]);
                i++;
            }
            i++;
            m=0;
            while (m<4) {
                value[m] = buf[i];
                m++;
                i++;
            }

            //printf(1,"valuse is: %s \n",value);
            args[j] = atoi(value);
            j++;
        }
    }
    close(fp);
    logFileDescriptor = open("ass2_log.txt",O_WRONLY);

    //The number of seats around the table, as well as the initial number of students will be given in a configuration file
    //and additional students might join the dining table during the simulation.
    //All buffers are full when the simulations start.
    food[SALAD] = args[Salad_Buffer_Size];
    food[PASTA] = args[Pasta_Buffer_Size];
    food[STEAK] = args[Steak_Buffer_Size];
    foodMAX[SALAD] = args[Salad_Buffer_Size];
    foodMAX[PASTA] = args[Pasta_Buffer_Size];
    foodMAX[STEAK] = args[Steak_Buffer_Size];
    
    //A group of n hungry students arrive at the restaurant
    //Each Student has its unique id (stud_id), starting from 0 in consecutive manner.

    //There is a table with k seats around it.
    restaurantMAX[TABLE] = args[Num_of_seats];
    
    //If n>k the remaining students will have to wait until any seats will become available.
    if (args[Students_Initial] > args[Num_of_seats]) {
        //k of them take seats around the table.
        restaurant[TABLE] = args[Num_of_seats];
    }else{
        restaurant[TABLE] = args[Students_Initial];
    }
    
    restaurant[STUDENTS] = args[Students_Joining] + args[Students_Initial];
    restaurantMAX[STUDENTS] = args[Students_Joining] + args[Students_Initial];
    totalThreadsNum = args[Num_of_seats] + 4;

    idMAX = restaurant[TABLE]-1;//what is the next id that i can alloc
    int studThreadsIds[restaurant[TABLE]];//this array is for  each thread to know to which student id is he assigned
    studThreadsIds_=studThreadsIds;
    
    //To model the problem, a thread should be created for each dinning student, each waiter and the host.
    //If k marks the number of seats around the table, then k+4 threads should be created.
    int ind = 0;
    while (ind < args[Num_of_seats]) {
        uthread_create(&accuireFood,p_normal);
        if (ind<restaurant[TABLE]) {
            studThreadsIds_[ind] = ind;
            ind++;            
        }else{
            studThreadsIds_[ind] = -1;
            ind++;
        }
    }
    
    uthread_create(&host,p_normal);
    uthread_create(&fillSalad,p_normal);
    uthread_create(&fillPasta,p_normal);
    uthread_create(&fillSteak,p_normal);

    
    uthread_start_all();

    printf(2, "\nshouldn't be here...!!!!!\n");
    exit();
    return 1;
}

